﻿using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using WAU.Utils;

namespace WAU.Common
{
    /// <summary>
    /// 自定的异常处理
    /// </summary>
    public class CustomExceptionHandler
    {
        /// <summary>
        /// 发生异常时的操作（异步）
        /// </summary>
        /// <param name="context"></param>
        /// <param name="exception"></param>
        /// <returns></returns>
        public static async Task OnExceptionAsync(HttpContext context, Exception exception)
        {
            // 获取请求中的 日志 服务组件
            ILogger<CustomExceptionHandler> logger = context.RequestServices.GetService<ILogger<CustomExceptionHandler>>();

            context.Response.StatusCode = 500;  // 状态码置为 500

            Result result = null;

            // 数据库并发保存的问题
            if (exception is DbUpdateConcurrencyException)
            {
                result = new Result
                {
                    Success = false,
                    Msg = "数据已经被编辑，请重新进入页面后重试"
                };
            }
            else
            {
                result = new Result
                {
                    Success = false,
                    Msg = exception.Message
                };
                logger.LogError(HandleExceptionContent(context, exception));
            }

            await context.Response.WriteAsync(JsonUtils.JsonSerializeForFront(result));
        }

        /// <summary>
        /// 过滤掉不重要的堆栈信息
        /// </summary>
        /// <param name="httpContext"></param>
        /// <param name="ex"></param>
        /// <returns></returns>
        private static string HandleExceptionContent(HttpContext httpContext, Exception ex)
        {
            // 请求地址
            string requestPath = Uri.UnescapeDataString(httpContext.Request.Path);

            string requestorName = httpContext.User.Identity.Name;    // 请求的用户名称（未登录则为null）
            string connectionId = httpContext.Connection.Id;    // 请求的连接id

            string requestIdentity = string.Format("{0} - {1}", requestorName, connectionId);
            string requestBodyStr = GetRequestContent(httpContext);

            return string.Format("{0}\n" +
                                "请求({1})：{2} 引发了错误！\n" +
                                "请求内容：{3} \n" +
                                "堆栈信息：{4} \n",
                                 FormatExceptionMsg(ex), requestIdentity, requestPath, requestBodyStr, FilterUnnecessaryMsg(ex.StackTrace));
        }

        /// <summary>
        /// 获取请求内容
        /// </summary>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        private static string GetRequestContent(HttpContext httpContext)
        {
            string requestBodyStr = string.Empty;
            Stream requestBodyStream = httpContext.Request.Body;

            // 请求内容流此时已经被读取（即当前位置大于0，表明处理请求时出错）才会记录
            if (requestBodyStream.CanSeek && requestBodyStream.Position > 0)
            {
                requestBodyStream.Seek(0, SeekOrigin.Begin);    // 需要相应中间件的支持，否则 Request.Body 无法 seek
                using (StreamReader sr = new StreamReader(requestBodyStream))
                {
                    requestBodyStr = sr.ReadToEndAsync().Result;
                }
            }
            else if (httpContext.Request.HasFormContentType)
            {
                requestBodyStr = string.Join("&", httpContext.Request.Form.Keys.Select(key => $"{key}={httpContext.Request.Form[key]}"));
            }

            return requestBodyStr;
        }

        /// <summary>
        /// 组成异常消息（并逐层查找内部异常）
        /// </summary>
        /// <param name="ex"></param>
        /// <returns></returns>
        private static string FormatExceptionMsg(Exception ex)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(ex.Message + "\t");
            Exception exp = ex;
            while (exp.InnerException != null)
            {
                sb.Append(exp.InnerException.Message + "\t");
                exp = exp.InnerException;
            }
            return sb.ToString();
        }

        /// <summary>
        /// 过滤掉不必要的堆栈信息
        /// </summary>
        /// <param name="exceptionStackTrace"></param>
        /// <returns></returns>
        private static string FilterUnnecessaryMsg(string exceptionStackTrace)
        {
            Regex regex = new Regex(@"\s+at\sMicrosoft.AspNetCore.Mvc.Infrastructure.*?((\r\n)|$)");
            return regex.Replace(exceptionStackTrace, "");
        }
    }
}
